import CryptoJS from "crypto-js";
import type {Options, Param, UFile} from "@/util/definition";
import {EncryptType, FileSet, FileStatus, UDir} from "@/util/definition";
import {calSha256} from "@/util/calHash";


const setValIfNull = (options: Options, name: "thread" | "chunkSize" |
    "immediate" | "encryptType", value: any) => {
    if ((!options[name] && options[name] !== false) || options[name]?.toString().trim() === "") {
        options[name] = value as never;
    }
};
export default class Uploader {
    private options: Options = {
        thread: 1,
        chunkSize: Number.MAX_VALUE,
        immediate: true,
        encryptType: EncryptType.NO,
        target: "",
        testTarget: "",
    };
    // private readonly uf = new UF();
    readonly fileList: FileSet = new FileSet();//总的文件列表
    readonly uploadingFileList: UFile[] = [];//正在上传的文件队列
    readonly successFileList: UFile[] = [];//成功的文件列表
    readonly failFileList: UFile[] = [];//失败的文件列表
    private readonly workerSet: Set<Worker> = new Set<Worker>();//线程列表
    private readonly freeWorkerSet: Set<Worker> = new Set<Worker>();//空闲线程列表
    // private readonly dispatchWork: Worker;

    constructor(options: Options) {
        this.options = {...this.options, ...options}
        setValIfNull(this.options, "thread", 1);
        setValIfNull(this.options, "chunkSize", Number.MAX_VALUE);
        setValIfNull(this.options, "immediate", true);
        setValIfNull(this.options, "encryptType", EncryptType.NO);
        console.log(this.options)
        //创建上传文件的input元素
        this.inputElementSupportFIle = this.createInputElement();
        //创建上传文件夹的input元素
        this.inputElementSupportDirectory = this.createInputElement();
        this.inputElementSupportDirectory.setAttribute("webkitdirectory", "true");
        // this.dispatchWork = new Worker(Uploader.createURL(Uploader.dispatch));

        //监听选择文件事件
        this.inputElementSupportFIle.addEventListener('change', (event) => {
            // if (this.options.preview) {
            //     this.options.preview();
            // }
            //@ts-ignore
            for (let file of event.target.files) {
                const uFile = file as UFile;
                // console.log();
                // this.dispatchWork.postMessage(file)
                uFile.hasher = Uploader.createHasher(this.options.encryptType)

                // @ts-ignore
                uFile.chunkSize = this.options.chunkSize;
                // @ts-ignore
                uFile.totalChunks = Math.ceil(uFile.size / this.options.chunkSize)
                console.log()
                this.totalChunks = this.totalChunks + uFile.totalChunks;
                uFile.status = FileStatus.CHECK//校验hash
                uFile.nextChunkNumber = 0
                calSha256(uFile, this.options.calHashCallback)
                if (this.options.immediate) {
                    uFile.status = FileStatus.WAITING//等待上传

                } else {
                    uFile.status = FileStatus.PAUSE//暂停状态
                }
                if (this.options.addFile) {
                    this.options.addFile(uFile)
                }
                this.fileList.add(uFile);
            }
            if (this.options.addFiles) {
                this.options.addFiles(this.fileList)
            }
            console.log(this.fileList)
            if (this.options.immediate) {
                console.log("__________+++++++++++")
                setTimeout(()=>{
                    const total = this.freeWorkerSet.size + this.workerSet.size;
                    // @ts-ignore
                    for (let count = total; count < this.options.thread; count++) {
                        const worker: Worker = new Worker(Uploader.createURL(Uploader.task));
                        this.freeWorkerSet.add(worker);
                        worker.onmessage = (event) => {//线程上传分片已经完毕
                            this.process(worker);
                        }
                    }
                    //子线程通知主线程分发任务
                    if (this.freeWorkerSet.size !== 0) {
                        for (let worker of this.freeWorkerSet) {
                            console.log("+++++++++")
                            this.process(worker);
                        }
                    }
                },10)
                // new Promise((resolve, reject) => {
                //     try {
                //         const total = this.freeWorkerSet.size + this.workerSet.size;
                //         // @ts-ignore
                //         for (let count = total; count < this.options.thread; count++) {
                //             const worker: Worker = new Worker(Uploader.createURL(Uploader.task));
                //             this.freeWorkerSet.add(worker);
                //             worker.onmessage = (event) => {//线程上传分片已经完毕
                //                 this.process(worker);
                //             }
                //         }
                //         //子线程通知主线程分发任务
                //         if (this.freeWorkerSet.size !== 0) {
                //             for (let worker of this.freeWorkerSet) {
                //                 console.log("+++++++++")
                //                 this.process(worker);
                //             }
                //         }
                //         resolve(null)
                //     } catch (e) {
                //         reject(e)
                //     }
                // })
                // this.dispatchWork.postMessage(true)
            }
        });
        this.inputElementSupportDirectory.addEventListener('change', (event) => {
            const uDir: UDir = new UDir();
            //@ts-ignore
            for (let file of event.target.files) {
                const uFile = file as UFile;
                uFile.hasher = Uploader.createHasher(this.options.encryptType)
                // @ts-ignore
                uFile.chunkSize = this.options.chunkSize;
                // @ts-ignore
                uFile.totalChunks = Math.ceil(uFile.size / this.options.chunkSize)
                this.totalChunks = this.totalChunks + uFile.totalChunks;
                uFile.status = FileStatus.CHECK//校验hash
                // calSha256(uFile, this.options.calHashCallback)
                if (this.options.immediate) {
                    uFile.status = FileStatus.WAITING//等待上传

                } else {
                    uFile.status = FileStatus.PAUSE//暂停状态
                }
                if (this.options.addFile) {
                    this.options.addFile(uFile)
                }
                uDir.push(uFile);
            }
            if (this.options.addFiles) {
                this.options.addFiles(this.fileList)
            }
            this.fileList.add(uDir);
            if (this.options.immediate) {
                // this.dispatchWork.postMessage(true)
            }
        });

        // this.dispatchWork.addEventListener('message', (e) => {
        //
        //     const total = this.freeWorkerSet.size + this.workerSet.size;
        //     // @ts-ignore
        //     for (let count = total; count < this.options.thread; count++) {
        //         const worker: Worker = new Worker(Uploader.createURL(Uploader.task));
        //         this.freeWorkerSet.add(worker);
        //         worker.onmessage = (event) => {//线程上传分片已经完毕
        //             this.process(worker);
        //         }
        //     }
        //     //子线程通知主线程分发任务
        //     if (this.freeWorkerSet.size !== 0) {
        //         for (let worker of this.freeWorkerSet) {
        //             console.log("+++++++++")
        //             this.process(worker);
        //         }
        //     }
        // }, false);
    }

    private process(worker: Worker) {
        if (this.freeWorkerSet.has(worker)) {
            this.freeWorkerSet.delete(worker);
            this.workerSet.add(worker);
        }
        let uFIle = this.fileList.getUploadFile();
        if (uFIle == null) {
            const time1 = new Date().getMilliseconds();
            while (true) {
                const time2 = new Date().getMilliseconds();
                if (time2 - time1 > 500) {
                    uFIle = this.fileList.getUploadFile();
                    break;
                }
            }
        }
        if (uFIle != null) {
            let currentChunkSize;
            if (uFIle.nextChunkNumber < uFIle.totalChunks - 1) {
                currentChunkSize = uFIle.chunkSize;
            } else {
                currentChunkSize = uFIle.size - (uFIle.nextChunkNumber + 1) * uFIle.chunkSize
            }
            let param: Param = {
                uniqueIdentifier: uFIle.uniqueIdentifier,
                totalChunks: uFIle.totalChunks,//总的片数
                chunkSize: uFIle.chunkSize,//分片大小
                currentChunkSize: currentChunkSize, //当前分片大小
                currentChunkNumber: uFIle.nextChunkNumber//下次应该上传第几片
            };
            worker.postMessage({file: uFIle, ...param});
            uFIle.nextChunkNumber++;
        } else {
            this.freeWorkerSet.add(worker);
            this.workerSet.delete(worker)
            if (this.freeWorkerSet.size !== 0) {
                for (let worker of this.freeWorkerSet) {
                    worker.terminate();
                    this.freeWorkerSet.delete(worker);
                }
            }
            console.log("全部上传完成")
            console.log(this.freeWorkerSet)
            console.log(this.workerSet)
        }
    }

    /**
     * 创建input元素
     */
    createInputElement(): Element {
        const randomId = this.randomstring(20);
        const inputElement = document.createElement('input');
        inputElement.id = randomId;
        inputElement.type = "file";
        inputElement.multiple = true;
        inputElement.accept = "*";
        inputElement.style.opacity = "0";
        inputElement.style.height = "0";
        inputElement.style.width = "0";
        // inputElement.addEventListener('input', (event) => {
        //     //
        //     // this.fileList.push(inputElement.files);
        // });
        return inputElement
    }

    public currentChunkNo: number = 0;//当前应该上传第几个分片
    public totalChunks: number = 0;


    /**
     * 暂停上传
     */
    public pause(uFile: UFile) {
        uFile.status === FileStatus.PAUSE;
        // for (let index = 0; index < this.fileList.length; index++) {
        //     let tmpUFile = this.fileList[index];
        //     if (uFile === tmpUFile) {
        //         this.uploadingFileList.splice(index, 1);
        //         this.fileList.push(uFile);
        //     }
        // }
    }

    /**
     * 继续上传
     */
    public resume() {

    }

    /**
     * 取消正在上传的文件,并且清空上传列表
     */
    public reset() {
        this.fileList.clear();
    }

    /**
     * 子线程处理逻辑 分发任务
     */
    private static dispatch(): void {
        self.addEventListener('message', (e) => {
            self.postMessage(e.data)
        }, false);
    }

    /**
     * 子线程处理逻辑 上传
     */
    private static task(): void {
        self.addEventListener('message', (e) => {
            self.postMessage('上传完成')
            console.log("此线程上传完成")
        }, false);

    }

    /**
     * 函数转换为URL
     */
    private static createURL(fn: Function): string {
        const blob = new Blob(['(function ' + fn.toString() + ')()'], {type: 'application/javascript'})
        return URL.createObjectURL(blob)
    }

    public bind(el: Element, supportDirectory?: boolean, accept?: string): void {
        el.addEventListener("click", () => {
            this.selectFile(el, supportDirectory, accept);
        })
        this.elements.push(el)
    }

    private randomstring(length: number): string {
        length = length || 30;
        const t = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        let n = "";
        for (let i = 0; i < length; i++) {
            n += t.charAt(Math.floor(Math.random() * t.length));
        }
        return n
    }


    /**
     * 执行此方法的永远是upload实例
     * @param el 触发事件的按钮
     * @param supportDirectory 是否支持上传目录
     * @param accept
     */
    private selectFile(el: Element, supportDirectory?: boolean, accept?: string): void {
        let inputElement;
        if (supportDirectory) {
            inputElement = this.inputElementSupportDirectory;
        } else {
            inputElement = this.inputElementSupportFIle;
        }
        if (accept) {
            // @ts-ignore
            inputElement.accept = accept;
        }
        const inputObj = document.getElementById(inputElement.id);
        if (!inputObj) {
            let parentNode = el.parentNode || document.body
            parentNode.insertBefore(inputElement, el);
        }

        // @ts-ignore
        inputElement.click();
    }


    /**
     * 创建一个加密对象，用于文件分片加密
     * 调用此对象的update()方法将需要加密的内容传入
     * 调用finalize()方法进行加密，再使用tostring(CryptoJS.enc.Hex)或tostring()方法转换为十六进制字符串
     * @param config
     */
    public static createHasher(config ?: EncryptType): any {
        switch (config) {
            case EncryptType.NO:
                return null;
            case EncryptType.MD5:
                return CryptoJS.algo.MD5.create();
            case EncryptType.SHA256:
                return CryptoJS.algo.SHA256.create();
            case EncryptType.SHA512:
                return CryptoJS.algo.SHA512.create();
            default:
                return CryptoJS.algo.SHA512.create();
        }
    }

    private readonly elements: Element[] = [];//触发事件的按钮元素，暂时没有使用
    private readonly inputElementSupportFIle: Element;//文件上传元素input
    private readonly inputElementSupportDirectory: Element;//文件上传元素input，支持上传文件夹
}
